r""" FRBR data models for eCataloger.

This module implements the data framework for representing records and
resources in eCataloger using the base FRBR specification located at
<http://www.ifla.org/files/cataloguing/frbr/frbr_2008.pdf>. The
Resource Description Framework (RDA)<http://www.rdaonline.org/>,
and FRBRoo specifications were used
to extend the basic FRBR classes and properties.
<http://cidoc.ics.forth.gr/frbr_drafts.html>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Copyright 2009,2010- Jeremy Nelson, Tomichi Informatics LLC
"""
__author__ = 'Jeremy Nelson'
__version__ = '1.2'

import datetime,os
import MODSData,PREMISData
from SearchData import EndeavorSearch
from basedata import *
from google.appengine.ext import db
from google.appengine.api import users
import logging

#=============================================================================#
# FRBR Properties                                                             #
#=============================================================================#
class Duration(object):
    """ Models duration from RDA Expression
       <http://www.rdaonline.org/Expression_6_1_09.pdf>.
    """
    types = {None:0,
             "seconds":1,
             "minutes":2,
             "hours":3,
             "days":4}

    reverse_types = {0:None,
                     1:"seconds",
                     2:"minutes",
                     3:"hours",
                     4:"days"}

    def __init__(self,
                 type_of=None,
                 value=None):
        if Duration.types.has_key(type_of):
            self.type_of = self.types[type_of]
        else:
            raise db.BadValueError('Duration.type %s must be one of these '
                                   'values %s' % (type_of,
                                                  Duration.types.keys()))
        if value is not None:
            self.value = value
        else:
            raise db.BadValueError('Duration.value cannot be None')

    def has_type(self):
        return self.type_of > 0

    def get_type(self):
        if self.type_of:
            return self.reverse_types[self.type_of]
        else:
            return None

    def __str__(self):
        return str(self.value)

    def __not__(self):
        return (not (self.has_type() or
                     self.value))

class DurationProperty(db.Property):

    data_type = Duration

    def get_value_for_datastore(self,model_instance):
        duration = super(DurationProperty,
                        self).get_value_for_datastore(model_instance)
        if duration:
            return [duration.type_of,duration.value]
        else:
            return None


    def make_value_from_datastore(self,value):
        if value is None:
            return None
        type_of = value[0]
        duration_value = value[1]
        duration_obj = Duration(value=duration_value)
        duration_obj.type = type_of
        return duration_obj

    def validate(self,value):
        if value is not None and not isinstance(value,Duration):
            raise db.BadValueError('Property %s must be convertable '
                                   'to a Duration instance (%s)',
                                   (self.name,value))
        return super(DurationProperty,self).validate(value)

    def empty(self,value):
        return not value
    
class Numeric(object):
    '''Models Numeric Design
ation found at 
       <http://www.ifla.org/VII/s13/frbr/frbr2.htm#4.2>
    '''

    types = {None:0,
             "opus":1,
             "serial":2,
             "thematic":3}

    reverse_types = {0:None,
                     1:"opus",
                     2:"serial",
                     3:"thematic"}

    def __init__(self,
                 type=None,
                 value=None,
                 authority=None):
        if Numeric.types.has_key(type):
            self.type = Numeric.types[type]
        else:
            raise db.BadValueError('Numeric.type %s must be one of these '
                                   'values %s' % (type,Numeric.types.keys()))
        if value is not None:
            self.value = value
        else:
            raise db.BadValueError('Numeric.value cannot be None')
        self.authority=authority

    def has_type(self):
        return self.type > 0

    def get_authority(self):
        if self.authority:
            return db.get(self.authority)
        else:
            return None

    def get_type(self):
        if self.type:
            return self.reverse_types[self.type]
        else:
            return None

    def __str__(self):
        return self.value
    
    def __not__(self):
        return (not (self.has_type() or
                     self.value or
                     self.authority))

class NumericProperty(db.Property):

    data_type = Numeric

    def get_value_for_datastore(self,model_instance):
        numeric = super(NumericProperty,
                        self).get_value_for_datastore(model_instance)
        if numeric:
            return [numeric.type,numeric.value,numeric.authority]
        else:
            return None


    def make_value_from_datastore(self,value):
        if value is None:
            return None
        type = value[0]
        num_value = value[1]
        authority = value[2]
        numeric_obj = Numeric(value=num_value,
                              authority=authority)
        numeric_obj.type = type
        return numeric_obj

    def validate(self,value):
        if value is not None and not isinstance(value,Numeric):
            raise db.BadValueError('Property %s must be convertable '
                                   'to a Numeric instance (%s)',
                                   (self.name,value))
        return super(NumericProperty,self).validate(value)

    def empty(self,value):
        return not value

class ReductionRatio(object):

    def __init__(self,
                 value=None,
                 ratio_range=None):
        if value is None and ratio_range is None:
            raise db.BadValueError('ReductionRatio must have value or range')
        if value is not None and ratio_range is None:
            self.value = value
            self.start = None
            self.end = None
        elif value is None and ratio_range is not None:
            start = int(ratio_range[0])
            end = int(ratio_range[1])
            if end < start:
                raise db.BadValueError('ReductionRatio end:%s cannot be less '
                                       'than start:%s' %
                                       (end,start))
            self.start,self.end = start,end
            self.value=self.start
        else:
            self.value = value
            self.start=int(ratio_range[0])
            self.end=int(ratio_range[1])
        if self.start and self.start != int(self.value):
            raise db.BadValueError('ReductionRatio value:%s must equal start:%s' %
                                   (value,self.start))

    def has_range(self):
        if self.start and self.end:
            return True
        else:
            return False

    def get_start(self):
        if self.has_range():
            return self.start
        else:
            return None

    def get_end(self):
        if self.has_range():
            return self.end
        else:
            return None

    def get_range(self):
        if self.has_range():
            return self.start,self.end
        else:
            return None

    def __str__(self):
        if self.has_range():
            return (str(self.start),str(self.end))
        elif self.value is not None:
            return str(self.value)
        else:
            return None

    def __not__(self):
        return (not (self.has_range() or
                     self.value))

class ReductionRatioProperty(db.Property):

    data_type = ReductionRatio

    def get_value_for_datastore(self,model_instance):
        reduction_ratio = super(ReductionRatioProperty,
                                self).get_value_for_datastore(model_instance)
        if reduction_ratio.__class__ == int:
            return reduction_ratio
        elif reduction_ratio.__class__ == list:
            return [reduction_ratio.start,reduction_ratio.end]
        else:
            return None

    def make_value_from_datastore(self,value):
        if value is None:
            return None
        if value.__class__ == int:
            reduction_ratio = ReductionRatio(value=value)
        elif value.__class__ == list:
            start = value[0]
            end = value[1]
            reduction_ratio = ReductionRatio(start=start,
                                             end=end)
        return reduction_ratio

    def validate(self,value):
        if value is not None and not isinstance(value,ReductionRatio):
            raise db.BadValueError('Property %s must be convertable '
                                   'to a ReductionRatio instance (%s)' %
                                   (self.name,value))
        return super(ReductionRatioProperty,self).validate(value)

    def empty(self,value):
        return not value

           
#=============================================================================#
# FRBR Models                                                                 #
#=============================================================================#
class Work(BaseModel):
    """ A distinct intellectual or artistic creation. A work is an abstract
        entity; there is no single material object one can point to as the
        work. FRBR specification pg. 17
    """
    marctarget_audiences = ["adolescent",
                            "adult",
                            "general",
                            "juvenile",
                            "pre-adolescent",
                            "preschool",
                            "primary",
                            "specialized"]    
    # Creators should come from supporting model
    creators = db.ListProperty(db.Key,default=None)
    consulted_sources = db.ListProperty(db.Key,default=None)
    context = db.StringProperty()
    content_coverage = db.ListProperty(db.Key,
                                       default=None)
    # FRBRoo property
    derivative = db.ReferenceProperty(collection_name='derivative_work')
    distinguishing_characteristics = db.ListProperty(db.Key,default=None)
    earliest_date = BaseDateProperty()
##    epoch = db.ReferenceProperty()
    form = db.StringProperty(required=False,
                             choices=(["biography",
                                       "braille",
                                       "concerto",
                                       "direct electronic",
                                       "drawing",
                                       "electronic",
                                       "essay",
                                       "large print",
                                       "map",
                                       "microfiche",
                                       "microfilm",
                                       "microopaque",
                                       "novel",
                                       "online",
                                       "painting",
                                       "photograph",
                                       "play",
                                       "poem",
                                       "regular print",
                                       "serial",
                                       "sonata",
                                       "symphony"]))
    identifiers = db.ListProperty(db.Key,default=None)
    
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"])) #:From RDA pg.4 <http://www.rdaonline.org/Work_6_1_09.pdf> """
    intended_audiences = db.StringListProperty()
    is_marctarget = db.BooleanProperty()
    intended_termination = BaseDateProperty()
    nature_of_content = db.StringProperty()
    """ Can include the history of the work, cataloguer's note, and any
    other notes pretaining to the work following RDA enhanced
    properties."""
    notes = db.ListProperty(db.Key,default=None)
    organization_system = db.StringProperty()
##    place_of_origin = db.ReferenceProperty()
    # FRBRoo property, should be limited to only Self-Contained
    # Expression
    realized = db.ReferenceProperty(collection_name="self_contained_expression")
    representive = db.ReferenceProperty(collection_name="self_contained_representive_expression")
    # FRBRoo property
    successor = db.ReferenceProperty(collection_name='successor_work')
    # Subject can be another Work, Expression, Manifestation, Item,
    # Person, Corporate Body, Concept, Object, Event, or Place
    subjects = db.ListProperty(db.Key,default=None)
    titles = db.ListProperty(db.Key,default=None)
    treaty_signatories = db.ListProperty(db.Key,default=None)

    def get_title(self):
        """ Returns uniform title of work, otherwise returns
            first title in list. """
        if len(self.titles) > 0:
            main_title = None
            for title_key in self.titles:
                title = db.get(title_key)
                if title.type == 'uniform':
                    main_title = title
                else:
                    pass
            if not main_title:
                main_title = db.get(self.titles[0])
            return main_title.title
        else:
            return None

    def get_titles(self):
        ''' Returns list of all titles associated with work.'''
        return self.get_collection(property_name=\
                                   'titles')

    def get_subjects(self):
        ''' Returns list FRBRData.Concept or MODSData.subject entities
            associated with the Work.'''
        return self.get_collection(property_name=\
                                   'subjects')

    def get_creators(self):
        ''' Returns list of entities that created this Work. Can be
            FRBRData.Person or FRBRData.CorporateBody entities.'''
        return self.get_collection(property_name=\
                                   'creators')

    def get_characteristics(self):
        ''' Returns list of entities that provide distinguishing
            characteristics of this Work.'''
        return self.get_collection(property_name=\
                                   'distinguishingCharacteristics')

    def get_identifiers(self):
        ''' Returns list of MODSData.identifier entities associated with
            this Work.'''
        return self.get_collection(property_name=\
                                   'identifiers')

    def get_treaty_signatories(self):
        ''' Returns list of entities that are treaty signatories for this
            Work.'''
        return self.get_collection(property_name=\
                                   'treaty_signatories')

    def get_notes(self):
        ''' Returns list of MODSData.note entities associated with this Work.'''
        return self.get_collection(property_name=\
                                   'notes')

    def is_realized_in(self):
        ''' Returns Expression that Work is realized in, otherwise return None.'''
        if self.realized:
            return self.realized
        else:
            return None


    def put(self):
        '''Iterates through indendedAudience if present,
        raises valueerror if audience is not in marc target. Sets Work
        display_form from get_title if not present.'''
        if self.intended_audiences and self.is_marctarget:
            for audience in self.intended_audiences:
                self.marctarget_audiences.index(audience)
        if not self.display_form:
            self.display_form = self.get_title()
        return db.Model.put(self)

class Creators(BaseModel):
    ''' Supporting model for Work associates an entity (Person, Event, or
        CorporateBody with a MODS role. '''
    creator = db.ReferenceProperty()
    roles = db.ListProperty(db.Key,
                            default=None)

    def get_roles(self):
        return self.get_collection(property_name="roles")

    def put(self):
        if not self.display_form:
            label = ''
            if hasattr(self.creator,'display_label'):
                label += self.creator.display_label
            elif hasattr(self.creator,'display_form'):
                label += self.creator.display_form
            label += ': '
            for role in self.get_roles():
                label += "%s " % role.type
            self.display_form = label
        return db.Model.put(self)


class ComplexWork(Work):
    ''' FRBRoo specification of a Complex Work.'''
    members = db.ListProperty(db.Key,
                              default=None)

    def get_members(self):
        return self.get_collection(property_name='members')

    def has_member(self,unknown_member):
        for member in self.get_members():
            if member.key() == unknown_member.key():
                return True
        return False

class ContainerWork(Work):
    ''' FRBRoo specification of a multiple works "enhance or add value to
        expressions from one or more other works without altering them, by
        the selection, arrangement and/or addition of features of different
        form, such as layout to words, recitation and movement to texts,
        instrumentation to musical scores etc." (6/2009 draft, pg.44)'''

    works = db.ListProperty(db.Key,
                            default=None)

    def get_works(self):
        return self.get_collection(property_name='works')


class IndividualWork(Work):
    ''' FRBRoo specification of an Individual work for works that
        "are realized by one and only one self-contained expression.
        (FRBR spec, pg. 43)'''
    

    def put(self):
        if not isinstance(self.is_realized_in(),SelfContainedExpression):
            raise db.BadValueError("IndividualWork.realized must be a "\
                                   "SelfContainedExpression, expression "\
                                   "class is %s" % type(self.is_realized_in()))
                                   
        return super(Work,self).put()
                                       

class AggregationWork(Work):
    ''' FRBRoo specification of an aggregation work "essence is the selection
        and/or arrangement of expressions of one or more other works.
        (pg. 45 6/2009 spec).'''
    members = db.ListProperty(db.Key,
                              default=None)

    def get_members(self):
        return self.get_collection(property_name='members')

    def put(self):
        for member in self.get_members():
            if str(member.__class__).lower() == str(ContainerWork).lower() or\
               str(member.__class__).lower() == str(IndividualWork).lower():
                pass
            else:
                raise db.BadValueError("All member Works in an AggregationWork "\
                                       "instance must be either a ContainerWork "\
                                       "or IndividualWork, member class is %s" %\
                                       (str(type(member))))
        return super(Work,self).put()
                                       

class PublicationWork(ContainerWork):
    ''' FRBRoo specification for a PublicationWork, for "works that have
        been planned to result in a manifestation product type and that
        pertain to the rendering of expressions from other works."
        (pg.45,46 6/2009 spec.)'''

    def put(self):
        if self.form:
            publication_forms = ["biography",
                                 "essay",
                                 "novel",
                                 "play",
                                 "poem",
                                 "serial"]
            if not publication_forms.count(self.form):
                raise db.BadValueError("PublicationWork.form must be: %s\n"\
                                       " instance form is %s" %\
                                       (publication_forms,self.form))
        return super(ContainerWork,self).put()
    

class SerialWork(ComplexWork,PublicationWork):
    ''' FRBRoo specification of a Serial Work as works that "works that are,
        or have been, planned to result in sequences of manifestations
        with common features." (pg.45 6/2009 spec.)'''
    has_issuing_rule = db.ReferenceProperty(collection_name='work_design_or_procedure')

    def put(self):
        if not self.form:
            self.form = "serial"
        # Check to see all works are also members
        for work_key in self.works:
            if not self.members.count(work_key):
                self.members.append(work_key)
        return super(PublicationWork,self).put()

class PerformanceWork(ContainerWork):
    ''' FRBRoo specification of a PerformanceWork, a work that "comprises
        the sets of concepts for rendering a particular or a series of like
        performances." (pg.46  6/2009 spec.)'''

    def put(self):
        if self.is_realized_in():
            expression = self.is_realized_in()
            if not isinstance(expression,PerformancePlan):
                raise db.BadValueError("PerformanceWork.realized expression "\
                                       "must be a PerformancePlan, realized "\
                                       "class is %s" % type(expression))
        return super(ContainerWork,self).put()

class RecordingWork(ContainerWork):
    ''' FRBRoo specification of a RecordingWork a work "conceptualise the
        capturing of features of perdurants." (pg.46 6/2009 spec.)'''
    composition_forms = db.StringListProperty()

    def put(self):
        if self.is_realized_in():
            expression = self.is_realized_in()
            if not isinstance(expression,Recording):
                raise db.BadValueError("RecordingWork.realized expression "\
                                       "must be a Recording, expression class "\
                                       " is %s" % type(self.expression))
        return super(ContainerWork,self).put()

class CartographicWork(ContainerWork):
    ''' Models a work such as a map.'''
    coordinates = db.GeoPtProperty()
    all_coordinates = db.ListProperty(db.Key,
                                      default=None)
    equinox = BaseDateProperty()
    right_ascension = db.StringProperty()
    declination = db.StringProperty()

    def put(self):
        if not self.form:
            self.form = 'map'
        return db.Model.put(self)

class DissertationThesisWork(ContainerWork):
    ''' Models a dissertation or thesis as a work, following RDA pg.5
        <http://www.rdaonline.org/Work_6_1_09.pdf> for special work.'''
    type = db.StringProperty(required=True,
                             choices=(["dissertation",
                                       "thesis"]))
    degree_granted = BaseDateProperty()
    academic_degree = db.StringProperty()
    granting_body = db.ReferenceProperty()

class ShippingListWork(ContainerWork):
    """ Base class for federal and Colorado state shipping lists.
    """
    processed_by = db.UserProperty()
    processed_on = db.DateProperty()
    list_date = db.DateProperty()
    number = db.StringProperty(required=True)


class FederalShippingListWork(ShippingListWork):
    """ Models a federal document shipping list, all individual items
        contained in the shipping list are included as IndividualWorks
        in this class "works" property."""
    type_of = db.StringProperty(required=True,
                                choices=(["print",
                                          "microfiche",
                                          "electronic",
                                          "blm",
                                          "dod",
                                          "forest service",
                                          "other"]))
        
class ColoradoShippingListWork(ShippingListWork):
    """ Models a Colorado document shipping list, all individual items
        contained in this shipping list are IndividualWorks in the class.
    """
    type_of = db.StringProperty(required=True,
                                choices=(["standard",
                                          "stateLINC",
                                          "other"]))

    

class MusicalWork(Work):
    composition_forms = db.StringListProperty()
    key_of = db.StringProperty()
    # Medium values come from page 2<http://www.rdaonline.org/Work_6_1_09.pdf>
    medium = db.StringProperty(required=False,
                               choices=(["piano strings: piano trio",
                                         "piano strings: piano quartet",
                                         "piano strings: piano quintet",
                                         "strings trio",
                                         "strings quartet",
                                         "woodwind quartet",
                                         "keyboard instrument",
                                         "continuo",
                                         "organs (2)",
                                         "pianos (2), 8 hands",
                                         "pianos (2)",
                                         "piano, 4 hands",
                                         "piano",
                                         "brasses",
                                         "instrumental ensemble",
                                         "keyboard instruments",
                                         "percussion",
                                         "plucked instruments",
                                         "strings",
                                         "winds",
                                         "woodwinds",
                                         "orchestra",
                                         "string orchestra",
                                         "alto",
                                         "baritone",
                                         "base",
                                         "mezzo-soprano",
                                         "soprano",
                                         "tenor",
                                         "men's solo voices",
                                         "mixed solo voices",
                                         "women's solo voices",
                                         "men's voices",
                                         "mixed voices",
                                         "unison voices",
                                         "women's voices",
                                         "accompanied",
                                         "unaccompanied",
                                         "band"]))
    numeric = NumericProperty()

    
class Person(BaseModel):
    '''Agent models information specific to a person including
       names, important dates (birth, death), and other information.

       Also includes <http://www.rdaonline.org/Person_6_1_09.pdf>
       and 
       
    '''
    active_period = db.ReferenceProperty(DateRangeModel,
                                         collection_name="active_period")
    addresses = db.ListProperty(db.Key,
                                default=None)
    affiliations = db.ListProperty(db.Key,
                                   default=None)
    authority = db.StringProperty(required=False,
                                  choices=(["naf",
                                            "sanb"]))
    birth = BaseDateProperty()
    birth_location = db.ReferenceProperty(collection_name="birth_location_")
    death = BaseDateProperty()
    death_location = db.ReferenceProperty(db.Model,
                                          collection_name="death_location_")
    description = db.ListProperty(db.Key,
                                  default=None)
    gender = db.StringProperty(required=False,
                               choices=(["female",
                                         "male",
                                         "transgender",
                                         "not known"]))    
    given = db.StringProperty()
    family = db.StringProperty(required=True)
    former_family = db.StringListProperty(default=[])
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"]))
    identifiers = db.ListProperty(db.Key,
                                  default=None)
    middle = db.StringProperty()
    nickname = db.StringProperty()
    notes = db.ListProperty(db.Key,
                            default=None)
    professional_fields = db.StringListProperty()
    occupations = db.StringListProperty()
    suffix = db.StringListProperty(default=None)
    terms_of_address = db.StringListProperty(default=None)
    type = db.StringProperty(default="person")

    def get_addresses(self):
        return self.get_collection(property_name="addresses")

    def get_affiliations(self):
        return self.get_collection(property_name="affiliations")

    def get_description(self):
        return self.get_collection(property_name="description")

    def get_identifiers(self):
        return self.get_collection(property_name="identifiers")

    def get_notes(self):
        return self.get_collection(property_name="notes")



    def put(self):
        if not self.display_form:
            self.display_form = "%s %s" % (self.given,
                                           self.family)
        return db.Model.put(self)

class CorporateBody(BaseModel):
    '''Models a corporate body

    '''
    address = db.PostalAddressProperty()
    associated_institution = db.SelfReferenceProperty()
    authority_rating = db.RatingProperty()
    authority = db.StringProperty(required=False,
                                  choices=(["naf",
                                            "sanb"]))
    codes = db.StringListProperty()
    dissolution_date = BaseDateProperty()
    held_on = BaseDateProperty()
    identifiers = db.ListProperty(db.Key,
                                  default=None)
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"]))
    incorporation_date = BaseDateProperty()
    name = db.StringProperty()
    notes = db.ListProperty(db.Key,
                            default=None)
    number = db.StringProperty()
    place = db.ListProperty(db.Key,
                            default=None)
    url = db.LinkProperty()
    alt_url = db.LinkProperty()
    
    description = db.ListProperty(db.Key,
                                  default=None)
    type = db.StringProperty(default="corporate")

    def get_codes(self):
        return self.codes
    
    def get_description(self):
        return self.get_collection(property_name="description")

    def get_identifiers(self):
        return self.get_collection(property_name="identifiers")

    def get_notes(self):
        return self.get_collection(property_name="notes")
    
    def get_place(self):
        return self.get_collection(property_name="place")

    def put(self):
        if not self.display_form and self.name:
            self.display_form = self.name
        return db.Model.put(self)


class Concept(BaseModel):
    '''Models a concept by associating a MODS subject model instance
       with children and parents relationships.
    '''
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"]))
    notes = db.ListProperty(db.Key,
                            default=None)
    preferred_term = db.CategoryProperty()
    varient_term = db.CategoryProperty()
    subject = db.ReferenceProperty(MODSData.subject)
    parent_concepts = db.ListProperty(db.Key,
                                      default=None)
    children_concepts = db.ListProperty(db.Key,
                                        default=None)

    def get_parents(self):
        return self.get_collection(property_name="parent_concepts")

    def get_children(self):
        return self.get_collection(property_name="children_concepts")

    def get_notes(self):
        return self.get_collection(property_name="notes")

    def put(self):
        if not self.display_form and self.subject:
            self.display_form = self.subject.topic()
        return db.Model.put(self)

class TermsValidator:

    def validate(self,terms,class_name):
        for term in terms:
            entity = db.get(term)
            if not isinstance(entity,(Concept,MODSData.subject)):
                raise db.BadValueError("%s term is not"
                                       " a FRBRData.Concept or"
                                       " a MODSData.subject, "
                                       " term type is %s" %
                                       (class_name,
                                        type(entity)))
        return True


class FRBRObject(BaseModel):
    ''' Models a FRBR Object, terms should all be MODSData.subject models
        or Concept model instances.

    '''
    identifiers = db.ListProperty(db.Key,
                                  default=None)
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"]))
    notes = db.ListProperty(db.Key,
                            default=None)
    sources_consulted = db.ListProperty(db.Key,
                                        default=None)
    
    terms = db.ListProperty(db.Key,
                            default=None)

    def get_identifiers(self):
        return self.get_collection(property_name="identifiers")

    def get_notes(self):
        return self.get_collection(property_name="notes")

    def get_sources_consulted(self):
        return self.get_collection(property_name="sources_consulted")

    def get_terms(self):
        return self.get_collection(property_name="terms")

    def put(self):
        TermsValidator().validate(self.terms,
                                  'FRBRObject')
        return db.Model.put(self)

class Place(BaseModel):
    '''
       Models a place
    '''
    identifiers = db.ListProperty(db.Key,
                                  default=None)
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"]))
    
    type_of = db.StringProperty(required=True,
                                choices=(["primary",
                                          "secondary",
                                          "other"]),
                                default="primary")
    postal_address = db.PostalAddressProperty()
    name = db.StringProperty()
    code = MODSData.geographicCodeProperty()
    coordinates = db.GeoPtProperty()
    description = db.TextProperty()
    notes = db.ListProperty(db.Key,
                            default=None)
    city = db.ReferenceProperty(MODSData.hierarchicalGeographic,
                                collection_name="city")
    terms = db.ListProperty(db.Key,
                            default=None)
    sources_consulted = db.ListProperty(db.Key,
                                        default=None)

    def get_identifiers(self):
        return self.get_collection(property_name="identifiers")

    def get_notes(self):
        return self.get_collection(property_name="notes")

    def get_sources_consulted(self):
        return self.get_collection(property_name="sources_consulted")

    def value(self):
        return self.name

    def put(self):
        if self.terms:
            TermsValidator().validate(self.terms,'Place')
        return db.Model.put(self)

class Event(BaseModel):
    '''Models an event.'''
    name = db.StringProperty()
    description = db.ListProperty(db.Key,
                                  default=None)
    held_on = BaseDateProperty()
    held_at = db.ListProperty(db.Key,
                              default=None)
    identifiers = db.ListProperty(db.Key,
                                  default=None)
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"]))    
    terms = db.ListProperty(db.Key,default=None)
    authority = db.StringProperty(required=False,
                                  choices=(["naf",
                                            "sanb"]))
    notes = db.ListProperty(db.Key,
                            default=None)
    type = db.StringProperty(default="event",
                             choices=(["conference",
                                       "event"]))
    sources_consulted = db.ListProperty(db.Key,
                                        default=None)

    def get_description(self):
        return self.get_collection(property_name="description")

    def get_identifiers(self):
        return self.get_collection(property_name="identifiers")

    def get_notes(self):
        return self.get_collection(property_name="notes")

    def get_sources_consulted(self):
        return self.get_collection(property_name="sources_consulted")

    def put(self):
        if self.held_at:
            for location_key in self.held_at:
                location = db.get(location_key)
                if not isinstance(location,(Place,
                                            MODSData.hierarchicalGeographic)):
                    raise db.BadValueError("Event must be a "
                                           "MODSData.hierarchicalGeographic "
                                           "instance held_at.type is %s" %
                                           (type(location),))
        if self.terms:
            TermsValidator().validate(self.terms,'Event')
        return db.Model.put(self)

class ExpressionCreation(Event):
    ''' FRBRoo class, class comprises activities that result in instances
        of F2 Expression coming into existence. This class characterises
        the externalisation of an Individual Work. (pg.50 6/2009 spec.)'''
    expression = db.ReferenceProperty(collection_name="created_expression")
    manifestation_singleton = db.ReferenceProperty(\
        collection_name="created_manifestation_singleton")
    realisation = db.ReferenceProperty(collection_name="realized_through_work")

    def was_created_by(self):
        if self.expression and self.manifestation_singleton:
            return (self.expression,self.manifestation_singleton)
        elif self.expression and not self.manifestation_singleton:
            return self.expression
        elif not self.expression and self.manifestation_singleton:
            return self.manifestation_singleton
        else:
            return None

    def was_realized_through(self):
        if self.realisation:
            return self.realisation
        else:
            return None

    def put(self):
        if self.manifestation_singleton:
            if not isinstance(self.manifestation_singleton,
                              ManifestationSingleton):
                raise db.BadValueError("ExpressionCreation.manifestation_singleton"\
                                       " class must be ManifestationSingleton, "\
                                       " manifestation_singleton class is %s" %
                                       type(self.manifestation_singleton))
        return super(Event,self).put()

class PublicationEvent(ExpressionCreation):
    ''' FRBRoo class, class comprises the activities of publishing.
        Such an event includes the creation of an F24 Publication Expression
        and setting up the means of production. (pg.51, 6/2009 spec.)'''
    created = db.ReferenceProperty(collection_name="publication_expression")
    realised = db.ReferenceProperty(collection_name="publication_work")

    def was_created_through(self):
        if self.created:
            return self.created
        else:
            return None

    def was_realised_through(self):
        if self.realised:
            return self.realised
        else:
            return None

    def put(self):
        if self.created:
            if not isinstance(self.created,PublicationExpression):
                raise db.BadValueError("PublicationEvent.created expression must be "\
                                       "a PublicationExpression, expression class is"\
                                       " %s" % type(self.created))
        if self.realised:
            if not isinstance(self.realised,PublicationWork):
                raise db.BadValueError("PublicationWork.realised Work must be "\
                                       " a PublicationWork, realised Work class "\
                                       "is %s" % type(self.realised))
        return super(ExpressionCreation,self).put()
                                   

class RecordingEvent(ExpressionCreation):
    ''' FRBRoo class, comprises activities that intend to convey (and preserve)
       the content of events in a recording, such as a live recording of a
       performance, a documentary, or other capture of a perdurant.
       (pg.50 6/2009 spec.)'''
    created = db.ReferenceProperty(collection_name="recording_expression")
    recorded = db.ReferenceProperty(collection_name="recording_event")

    def was_created_through(self):
        if self.created:
            return self.created
        else:
            return None

    def was_recorded_through(self):
        if self.recorded:
            return self.recorded
        else:
            return None

    def was_realized_through(self):
        if self.realization:
            return self.realization
        else:
            return None
    
    
class CarrierProductionEvent(Event):
    ''' FRBRoo class, comprises activities that result in instances of F5 Item
        coming into existence. The creation of a new copy of a file on an
        electronic carrier is also regarded as a Carrier Production Event.
        (pg. 52 6/2009 spec.)'''
    produced_types = db.ListProperty(db.Key,
                                      default=None)
    produced = db.ReferenceProperty(collection_name="produced_item")
    source_material = db.ListProperty(db.Key,
                                      default=None)

    def get_produced_types(self):
        return self.get_collection(property_name="produced_types")

    def get_source_material(self):
        return self.get_collection(property_name="source_material")

    def was_produced_by(self):
        return self.get_produced_types()

    def was_used_by(self):
        return self.get_source_material()

class ReproductionEvent(Event):
    ''' FRBRoo class, E84 Information Carrier (such as an F5 Item or an
        F4 Manifestation Singleton which is also instance of
        E84 Information Carrier), preserving the expression carried by it.
        (pg.52 06/2009 spec.)'''
    produced = db.ListProperty(db.Key,
                               default=None)
    reproduced = db.ListProperty(db.Key,
                                 default=None)

    def get_produced(self):
        return self.get_collection(property_name="produced")

    def get_reproduced(self):
        return self.get_collection(property_name="produced")
    
    def was_produced_by(self):
        if self.produced:
            return self.get_produced()
        else:
            return None

    def was_reproduced_by(self):
        if self.reproduced:
            return self.get_reproduced()
        else:
            return None

    def put(self):
        if self.produced:
            for entity in self.get_produced():
                if not isinstance(entity,(Item,ManifestationSingleton)):
                    raise db.BadValueError("ReproductionEvent.produced must be an "\
                                           "Item or ManifestationSingleton, produced "\
                                           "class is " % type(self.produced))
        if self.reproduced:
            for entity in self.get_reproduced():
                if not isinstance(entity,(Item,ManifestationSingleton)):
                    raise db.BadValueError("ReproductionEvent.reproduced must be an "\
                                           "Item or ManifestationSingleton, reproduced"\
                                           "class is " % type(self.produced))
        return super(Event,self).put()
    
class Expression(BaseModel):
    ''' The intellectual or artistic realization of a work in the form
        of alpha-numeric, musical, or choreographic notation, sound,
        image, object, movement, etc., or any combination of such forms.

        An expression is the specific intellectual or artistic form
        that a work takes each time it is "realized." pg. 19 Form list
        includes RDA <http://www.rdaonline.org/Expression_6_1_09.pdf>
        pg.2 content types.'''
    awards = db.StringListProperty()
    consulted_sources = db.ListProperty(db.Key,default=None)
    context = db.ListProperty(db.Key,default=None)
    critical_response = db.TextProperty()
    distinguishingCharacteristics = db.StringListProperty(default=None)
    earliest_date = BaseDateProperty()
    extent = db.ListProperty(db.Key,
                             default=None)
    form = db.StringProperty(required=False,
                             choices=(["alpha-numeric notation",
                                       "cartographic dataset",
                                       "cartographic",
                                       "cartographic moving image",
                                       "cartographic tactile image",
                                       "cartographic image",
                                       "cartographic tactile three-dimensional form",
                                       "cartographic three-dimensional form",
                                       "computer dataset",
                                       "computer program",
                                       "dance",
                                       "mixed material",
                                       "mime",
                                       "moving image",
                                       "musical notation",
                                       "musical sound",
                                       "notated movement",
                                       "notated music",
                                       "other",
                                       "performed music",
                                       "photographic image",
                                       "sculpture",
                                       "sounds",
                                       "spoken word",
                                       "still image",
                                       "tactile image",
                                       "tactile notated music",
                                       "tactile notated movement",
                                       "tactile text",
                                       "tactile three-dimensional form",
                                       "text",
                                       "three-dimensional form",
                                       "three-dimensional moving image",
                                       "two-dimensional moving image",
                                       "unspecified",
                                       "software, multimedia",                                       
                                       "sound recording-musical",
                                       "sound recording-nonmusical",
                                       "three dimensional object",
                                       "url"]))
    genre = db.ListProperty(db.Key,
                            default=None)
    identifiers = db.ListProperty(db.Key,default=None)
    identification_status = db.StringProperty(required=False,
                                              choices=(["fully established",
                                                        "provisional",
                                                        "preliminary"]))     
    illustrative_content = db.ListProperty(db.Key,default=None)
    is_collection = db.BooleanProperty()    
    is_extensible = db.BooleanProperty()
    is_manuscript = db.BooleanProperty()    
    is_revisable = db.BooleanProperty()
    issuance = db.StringProperty(default="monographic",
                                 choices=(["continuing",
                                           "monographic"]))
    languages = db.ListProperty(db.Key,default=None)
    movement_notation = db.StringProperty(required=False,
                                          choices=(["action stroke dance",
                                                    "beauchamp-feuillet",
                                                    "benesh movement",
                                                    "dance writing",
                                                    "eshkol-wachman",
                                                    "game play",
                                                    "labanotation",
                                                    "stepanov",
                                                    "other"]))
    # From RDA Expression Special pg.7, performers include
    # expression's performers, narrators, and/or presenters
    performers = db.ListProperty(db.Key,default=None)
    realized_by = db.ListProperty(db.Key,default=None)
    # From RDA Expression recommendation,
    # Using ISO 15924 codes
    # for controlled vocab for script property.
    # <http://www.unicode.org/iso15924/iso15924-text.html>
    script = db.StringProperty(required=False,
                               choices=(["Arab",
                                         "Armi",
                                         "Armn",
                                         "Avst",
                                         "Bali",
                                         "Bamu",
                                         "Batk",
                                         "Beng",
                                         "Blis",
                                         "Bopo",
                                         "Brah",
                                         "Brai",
                                         "Bugi",
                                         "Buhd",
                                         "Cakm",
                                         "Cans",
                                         "Cari",
                                         "Cham",
                                         "Cher",
                                         "Cirt",
                                         "Copt",
                                         "Cprt",
                                         "Cyrl",
                                         "Cyrs",
                                         "Deva",
                                         "Dsrt",
                                         "Egyd",
                                         "Egyh",
                                         "Egyp",
                                         "Ethi",
                                         "Geor",
                                         "Geok",
                                         "Glag",
                                         "Goth",
                                         "Grek",
                                         "Gujr",
                                         "Guru",
                                         "Hang",
                                         "Hani",
                                         "Hano",
                                         "Hans",
                                         "Hant",
                                         "Hebr",
                                         "Hira",
                                         "Hmng",
                                         "Hrkt",
                                         "Hung",
                                         "Inds",
                                         "Ital",
                                         "Java",
                                         "Jpan",
                                         "Kali",
                                         "Kana",
                                         "Khar",
                                         "Khmr",
                                         "Knda",
                                         "Kore",
                                         "Kthi",
                                         "Lana",
                                         "Laoo",
                                         "Latf",
                                         "Latg",
                                         "Latn",
                                         "Lepc",
                                         "Limb",
                                         "Lina",
                                         "Linb",
                                         "Lisu",
                                         "Lyci",
                                         "Lydi",
                                         "Mand",
                                         "Mani",
                                         "Maya",
                                         "Mero",
                                         "Mlym",
                                         "Moon",
                                         "Mong",
                                         "Mtei",
                                         "Mymr",
                                         "Nkgb",
                                         "Nkoo",
                                         "Ogam",
                                         "Olck",
                                         "Orkh",
                                         "Orya",
                                         "Osma",
                                         "Perm",
                                         "Phag",
                                         "Phli",
                                         "Phlp",
                                         "Phlv",
                                         "Phnx",
                                         "Plrd",
                                         "Prti",
                                         "Qaaa",
                                         "Qabx",
                                         "Rjng",
                                         "Roro",
                                         "Runr",
                                         "Samr",
                                         "Sara",
                                         "Sarb",
                                         "Saur",
                                         "Sgnw",
                                         "Shaw",
                                         "Sinh",
                                         "Sund",
                                         "Sylo",
                                         "Syrc",
                                         "Syre",
                                         "Syrj",
                                         "Syrn",
                                         "Tagb",
                                         "Tale",
                                         "Talu",
                                         "Taml",
                                         "Tavt",
                                         "Telu",
                                         "Teng",
                                         "Tfng",
                                         "Tglg",
                                         "Thaa",
                                         "Thai",
                                         "Tibt",
                                         "Ugar",
                                         "Vaii",
                                         "Visp",
                                         "Xpeo",
                                         "Xsux",
                                         "Yiii",
                                         "Zinh",
                                         "Zmth",
                                         "Zsym",
                                         "Zxxx",
                                         "Zyyy",
                                         "Zzzz"]))
    sound_content = db.StringProperty(required=False,
                                      choices=(["sound",
                                                "silent"]))
    summarizations = db.ListProperty(db.Key,
                                     default=None)
    supplementary = db.ListProperty(db.Key,
                                    default=None)
    tactile_notation = db.StringProperty(required=False,
                                         choices=(["braille code",
                                                   "computing braille code",
                                                   "mathematics braille code",
                                                   "mode code",
                                                   "music braille code",
                                                   "tactile graphic",
                                                   "tactile musical notation"]))
    title = db.ReferenceProperty(MODSData.titleInfo,
                                 collection_name="ExpressionTitle")
    use_restrictions = db.ListProperty(db.Key,default=None)
    urls = db.ListProperty(db.Key,
                           default=None)
    work = db.ReferenceProperty(Work,
                                collection_name="realized_work")

    def get_languages(self):
        return self.get_collection(property_name="languages")

    def get_use_restrictions(self):
        return self.get_collection(property_name="use_restrictions")

    def get_consulted_sources(self):
        return self.get_collection(property_name="consulted_sources")

    def get_realized_by(self):
        return self.get_collection(property_name="realized_by")

    def get_summarizations(self):
        return self.get_collection(property_name="summarizations")

    def get_supplementary(self):
        return self.get_collection(property_name="supplementary")

    def get_illustrative_content(self):
        return self.get_collection(property_name="illustrative_content")

    def get_performers(self):
        return self.get_collection(property_name="performers")

    def get_context(self):
        return self.get_collection(property_name="context")

    def get_genre(self):
        return self.get_collection(property_name="genre")

    def get_extent(self):
        return self.get_collection(property_name="extent")

    def get_urls(self):
        return self.get_collection(property_name="urls")

    def put(self):
        if self.realized_by:
            for value_key in self.realized_by:
                entity = db.get(value_key)
                if not isinstance(entity,(Person,CorporateBody)):
                    raise db.BadValueError('Expression realized_by '
                                           ' must be a Person or '
                                           'CorporateBody value is %s' %
                                           (type(entity)))
        if not self.display_form:
            if self.title is not None:
                self.display_form = self.title.title
        return db.Model.put(self)
    
class Summarization(db.Model):
    ''' Supporting model for Expression '''
    type = db.StringProperty(required=True,
                             choices=(["abstract",
                                       "chapter headings",
                                       "parts",
                                       "songs",
                                       "summary",
                                       "synopsis",
                                       "table of contents"]))
    date = BaseDateProperty()
    values = db.ListProperty(db.Key,
                             default=None)

    def get_values(self):
        if self.values:
            output = list()
            for value_key in self.values:
                output.append(db.get(value_key))
            return output
        else:
            return None

    def put(self):
        if self.type == 'abstract':
            for value in self.values:
                entity = db.get(value)
                if not isinstance(entity,MODSData.abstract):
                    raise db.BadValueError('''Summarization values
                                           must all be abstracts instances''')
        return db.Model.put(self)

class Supplementary(BaseModel):
    ''' Supporting model for Expression.'''
    type_of = db.StringProperty(required=False,
                                choices=(["index",
                                          "bibliography",
                                          "appendix",
                                          "site-map"]))
    values = db.ListProperty(db.Key,
                             default=None)

    def get_values(self):
        return self.get_collection(property_name="values")

class IllustrativeContent(BaseModel):
    ''' Supporting model for Expression, based RDA Expression's illustrative
        content, page 5 <http://www.rdaonline.org/Expression_6_1_09.pdf>'''
    type_of = db.StringProperty(required=False,
                                choices=(["charts",
                                          "coats of arms",
                                          "facsimiles",
                                          "forms",
                                          "genealogical tables",
                                          "illuminations",
                                          "illustration",
                                          "illustrations",
                                          "maps",
                                          "music",
                                          "phonodisc, phonowire, etc.",
                                          "photographs",
                                          "plans",
                                          "plates",
                                          "portraits",
                                          "samples"]))
    creators = db.ListProperty(db.Key,default=None)
    details = db.StringListProperty()

    def get_creators(self):
        return self.get_collection(property_name="creators")

class SelfContainedExpression(Expression):
    ''' FRBRoo specification of a SelfContainedExpression class that
        "comprises the immaterial realisations of individual works at
        a particular time that are regarded as a complete whole."
        (pg.47 6/2009 spec.)'''
    incorporated = db.ReferenceProperty(collection_name="incorporate_expression")

    def is_incorporated_in(self):
        if self.incorporated:
            return self.incorporated
        else:
            return None
        
class ExpressionFragment(Expression):
    ''' FRBRoo specification of an ExpressionFragment that "comprises parts of
        Expressions and these parts are not Self-contained Expressions
        themselves." (pg. 47 6/2009 spec.)'''
    fragment_parts = db.ListProperty(db.Key,
                                   default=None)
    

    def get_fragments(self):
        return self.get_collection(property_name='fragment_parts')

    def has_fragment(self,expression):
        if self.fragment_parts.count(expression.key()):
            return True
        else:
            return False

    def put(self):
        for expression in self.get_fragments():
            if isinstance(expression,SelfContainedExpression):
                raise db.BadValueError("ExpressionFragment parts cannot be "\
                                       "SelfContainedExpressions")
        return super(Expression,self).put()

class PerformancePlan(SelfContainedExpression):
    ''' Models FRBRoo's PerformancePlan expression as defined as "sets of
        directions to which individual performances of theatrical,
        choreographic, or musical works and their combinations should conform."
        (pg.49 6/2009 spec.)'''
    design = db.ReferenceProperty(collection_name="design_or_procedure")

class PublicationExpression(SelfContainedExpression):
    ''' Models FRBRoo's PublicationExpression that is "comprises the complete
        layout and content provided by a publisher (in the broadest sense of
        the term) in a given publication and not just what was added by the
        publisher to the authors' expressions." (pg. 48 6/2009 spec.)'''
    constituents = db.ListProperty(db.Key,
                                   default=None)

    def get_constituents(self):
        return self.get_collection(property_name="constituents")

    def put(self):
        if self.is_incorporated_in():
            if not self.constituents.count(self.incorporated.key()):
                self.constituents.append(self.incorporated.key())
        return super(SelfContainedExpression,self).put()

class Recording(SelfContainedExpression):
    ''' Models FRBRoo's Recording expression that "comprises expressions
        which are created in instances of F29 Recording Event. A recording
        is intended to convey (and preserve) the content of one or more
        events." (pg.49 48 6/2009 spec.)'''
    events = db.ListProperty(db.Key,
                             default=None)

    def get_events(self):
        return self.get_collection(property_name="events")
    
class Serial(PublicationExpression):
    '''A periodical models a periodic publication, can be a magazine, 
       journal, newspaper, or series in either physical or digital forms

    '''
    frequency = db.StringProperty(required=True,
                                  choices=(["annual",
                                            "bimonthly",
                                            "semiweekly",
                                            "daily",
                                            "biweekly",
                                            "semiannual",
                                            "biennial",
                                            "triennial",
                                            "three times a week",
                                            "three times a month",
                                            "continuously updated",
                                            "monthly",
                                            "quarterly",
                                            "semimonthly",
                                            "three times a year",
                                            "weekly",
                                            "completely irregular"]))
    is_current_subscription = db.BooleanProperty()
    issn = db.ReferenceProperty(MODSData.identifier,
                                collection_name="issn_id")
    issnl = db.ReferenceProperty(MODSData.identifier,
                                 collection_name="link_issn")
    online_issn = db.ReferenceProperty(MODSData.identifier,
                                       collection_name="issn_online")
    publisher = db.ReferenceProperty(CorporateBody)
    sequencing_pattern = db.StringProperty()

    def put(self):
        if not self.issuance:
            self.issuance = "continuing"
        if self.issn:
            if self.issn.type != 'issn':
                raise db.BadValueError("Serial.issn type must be issn not %s" %\
                                       self.issn.type)
        if self.online_issn:
            if self.online_issn.type != 'issn':
                raise db.BadValueError("Serial.online_issn type must be issn not %s" %\
                                       self.online_issn.type)
        if self.work:
            if not isinstance(self.work,SerialWork):
                raise db.BadValueError("Serial.work must be a SerialWork "\
                                       "instance, work's class is %s" %\
                                       self.work.__class__)
        return super(PublicationExpression,self).put()

class CartographicImageObject(Expression):
    """ This class models either a cartographic image or object

    """
    geodetic_measurement = db.StringProperty()
    grid_measurement = db.StringProperty()
    
    presentation_technique = db.StringProperty(required=False,
                                               choices=(["anaglyphic",
                                                         "diagrammatic",
                                                         "pictorial"]))
    projection = db.StringProperty(required=False,
                                   choices=(["azimuthal equidistant",
                                             "transverse mercator"]))
    
    scale = db.StringProperty(required=False,
                              choices=(["angular",
                                        "horizontal",
                                        "other",
                                        "vertical"]))
    scale_measurement = db.StringProperty()
    vertical_measurement = db.StringProperty()

    def put(self):
        if self.form:
            if not (self.form=="cartographic" or
            self.form == "cartographic moving image" or
            self.form == "cartographic tactile image" or
            self.form == "cartographic image"):
                raise db.BadValueError("CartographicImageObject form "\
                                       "cannot be %s" % self.form)
        else:
            self.form = "cartographic image"
        return db.Model.put(self)


class Graphic(Expression):
    ''' A grapic models a born digital or physical graphic
        Colour content from page 8
        <http://www.rdaonline.org/Expression_6_1_09.pdf>.
    '''
    colour_content = db.StringProperty(required=False,
                                       choices=(["black and white",
                                                 "gray scale",
                                                 "tinted",
                                                 "toned",
                                                 "tinted and toned",
                                                 "sepia"]))
    technique = db.StringProperty(required=False,
                                  choices=(["born digital",
                                            "engraving"]))

class Legal(Expression):
    ''' Models an expression of a legal work, from RDA Expression
        <http://www.rdaonline.org/Expression_6_1_09.pdf> pg.2.'''
    protocols = db.StringListProperty()
    
class MusicalNotation(Expression):
    ''' A musical notation models an expression of a music
        notation. Includes RDA Other Distinguishing Characteristics
        of the Expression of a Musical Work for type_of, from page 2
        of <http://www.rdaonline.org/Expression_6_1_09.pdf>.
    '''
    duration = DurationProperty()
    format_of = db.StringProperty(required=False,
                                  choices=(["choir book",
                                            "chorus score",
                                            "condensed score",
                                            "part",
                                            "piano conductor part",
                                            "piano score",
                                            "study score",
                                            "table book",
                                            "violin conductor part",
                                            "vocal score"]))
    notation_form = db.StringProperty(required=False,
                                      choices=(["graphic notation",
                                                "letter notation",
                                                "neumatie notation",
                                                "number notation",
                                                "staff notation",
                                                "tablature",
                                                "tonic sol-fa",]))
    medium_of_performance = db.StringProperty()
    type_of = db.StringProperty(required=False,
                                choices=(["accompaniment reduced for keyboard",
                                          "arranged",
                                          "chorus",
                                          "chorus score",
                                          "chorus scores",
                                          "close score",
                                          "condensed score",
                                          "condensed score or piano-conductor score",
                                          "full score",
                                          "full score, miniature or study size",
                                          "libretto",
                                          "librettos",
                                          "multiple score formats",
                                          "not applicable",
                                          "other",
                                          "performer-conductor part",
                                          "short score",
                                          "sketches",
                                          "text",
                                          "texts",
                                          "unknown",
                                          "vocal score",
                                          "vocal scores",
                                          "voice score"]))    

    def put(self):
        if self.form:
            if not (self.form == "musical notation" or
            self.form == "notated music"):
                raise db.BadValueError("MusicalNotation.form %s not valid" %\
                                       self.form)
        else:
            self.form = "musical notation"
        return db.Model.put(self)


class ProjectedImage(Expression):
    '''A projected images models a moving images. Includes RDA enhanced
       properties from <http://www.rdaonline.org/Expression_6_1_09.pdf>
       page 5.
    '''
    artistic_credits = db.ListProperty(db.Key,default=None)
    aspect_ratio = db.StringProperty(required=False,
                                     choices=(["full screen",
                                               "wide screen",
                                               "mixed"]))
    colour_content = db.StringProperty(required=False,
                                       choices=(["black and white",
                                                 "tinted",
                                                 "toned",
                                                 "tinted and toned",
                                                 "sepia"]))
    date_captured = BaseDateProperty()
    duration = DurationProperty()
    technique = db.StringProperty(required=False,
                                  choices=(["animation",
                                            "computer generation",
                                            "live action",
                                            "3D"]))
    technical_credits = db.ListProperty(db.Key,default=None)
    where_captured = db.ReferenceProperty()

    def get_artistic_credits(self):
        return self.get_collection(property_name="artistic_credits")

    def get_technical_credits(self):
        return self.get_collection(property_name="technical_credits")

    def get_credits(self):
        all_credits = self.get_artistic_credits() + self.get_technical_credits()
        return all_credits


class Religious(Expression):
    ''' Models an expression of a religious work, from RDA Expression
        <http://www.rdaonline.org/Expression_6_1_09.pdf> pg.2.'''
    confraternity = db.ReferenceProperty(CorporateBody,
                                         collection_name="confraternity")
    douai = db.ReferenceProperty(CorporateBody,
                                 collection_name="douai")

class RemoteSensingImage(Expression):
    ''' A remote sensing image models an expression of an image
        captured through remote sensing instrument.

    '''
    colour_content = db.StringProperty(required=False,
                                       choices=(["black and white",
                                                 "gray scale",
                                                 "tinted",
                                                 "toned",
                                                 "tinted and toned",
                                                 "sepia"]))
    recording_technique = db.StringProperty(required=False,
                                            choices=(["infrared line scanning",
                                                      "multispectral photography",
                                                      "passive microwave mapping",
                                                      "SLAR"]))
    special_characteristics = db.ListProperty(db.Key,default=None)

class Manifestation(BaseModel):
    reformating_qualities = ['access',
                             'preservation',
                             'replacement']
    acquisition_sources = db.ListProperty(db.Key,
                                          default=None)
    access_authorization_sources = db.ListProperty(db.Key,
                                                   default=None)
    access_restrictions = db.ListProperty(db.Key,
                                          default=None)
    availability_terms = db.StringListProperty()
    captured_on = BaseDateProperty()
    classifications = db.ListProperty(db.Key,
                                      default=None)
    copyright_date = BaseDateProperty()
    digital_origin = db.StringProperty(required=False,
                                       choices=(["born digital",
                                                 "reformatted digital",
                                                 "digitized microfilm",
                                                 "digitized other analog"]))
    distribution_date = BaseDateProperty()
    distributors = db.ListProperty(db.Key,
                                   default=None)
    edition = db.StringProperty()
    expressions = db.ListProperty(db.Key,
                                  default=None)
    extent = db.ListProperty(db.Key,
                             default=None)
    fabricators = db.ListProperty(db.Key,
                                  default=None)
    forms_of_carrier = db.ListProperty(db.Key,
                                       default=None)
    identifiers = db.ListProperty(db.Key,
                                  default=None)
    issuance_mode = db.StringProperty(required=False,
                                      choices=(["single unit",
                                                "multipart monograph",
                                                "serial",
                                                "integrating resource"]))
    manufacturers = db.ListProperty(db.Key,
                                    default=None)
    materials = db.ListProperty(db.Key,
                                default=None)
    media_type = db.StringProperty(required=False,
                                   choices=(["audio",
                                             "computer",
                                             "microform",
                                             "microscopic",
                                             "stereographic",
                                             "projected",
                                             "unmediated",
                                             "video",
                                             "other",
                                             "unspecified"]))
    modified_on = BaseDateProperty()
    notes = db.ListProperty(db.Key,
                            default=None)
    parallel_titles = db.ListProperty(db.Key,
                                      default=None)
    physical_mediums = db.StringListProperty(default=None)
    place_of_distribution = db.ReferenceProperty(Place,
                                                 collection_name="manifest_pod")
    place_of_publication = db.ListProperty(db.Key,
                                           default=None)
    popularity = db.RatingProperty()
    produced_by = db.ListProperty(db.Key,
                                  default=None)
    production_places = db.ListProperty(db.Key,
                                  default=None)
    publisher = db.ReferenceProperty(CorporateBody,
                                     collection_name="manifest_publisher")
    publication_date = BaseDateProperty()
    publication_date_start = BaseDateProperty()
    publication_date_end = BaseDateProperty()
    reformating_quality = db.StringListProperty()
    statement_of_responsibility = db.ListProperty(db.Key,
                                                  default=None)
    series = db.ListProperty(db.Key,
                             default=None)
    terms_of_availability = db.ListProperty(db.Key,
                                            default=None)
    title = db.ReferenceProperty(MODSData.titleInfo,
                                 collection_name="a_manifest_title")
    use_restrictions = db.ListProperty(db.Key,
                                       default=None)
    varient_titles = db.ListProperty(db.Key,
                                     default=None)
    valid_on = BaseDateProperty()


    def get_extent(self):
        return self.get_collection(property_name="extent")

    def get_forms_of_carrier(self):
        return self.get_collection(property_name="forms_of_carrier")
    
    def get_access_restrictions(self):
        return self.get_collection(property_name='access_restrictions')
 

    def get_callnumbers(self):
        return self.get_collection(property_name="classifications")
    
    def get_classifications(self):
        return self.get_collection(property_name='classifications')

    def get_expressions(self):
        return self.get_collection(property_name="expressions")

    def get_identifiers(self):
        return self.get_collection(property_name="identifiers")


    def get_materials(self):
        return self.get_collection(property_name="materials")

    def get_notes(self):
        return self.get_collection(property_name="notes")

    def get_parallel_titles(self):
        return self.get_collection(property_name='parallel_titles')

    def get_publication_places(self):
        return self.get_collection(property_name="place_of_publication")

    def get_series(self):
        return self.get_collection(property_name='series')

    def get_statement_of_responsibility(self):
        return self.get_collection(property_name="statement_of_responsibility")

    def get_use_restrictions(self):
        return self.get_collection(property_name="use_restrictions")

    def get_varient_titles(self):
        return self.get_collection(property_name='varient_titles')

    def put(self):
        if not self.display_form and self.title:
            self.display_form = self.title.title
        if self.produced_by:
            for producer_key in self.produced_by:
                entity = db.get(producer_key)
                if not isinstance(entity,(Person,CorporateBody)):
                    raise db.BadValueError('Manifestation produced_by must '
                                           'a Person or CorporateEntity '
                                           'instances, type is %s' %
                                           (type(entity),))
        if self.expressions:
            for expression_key in self.expressions:
                entity = db.get(expression_key)
                if not isinstance(entity,Expression):
                    raise db.BadValueError('Manifestation expressions must '
                                           'all be Expression instances, '
                                           'expression\'s type is %s' %
                                           (type(entity),))

        if self.reformating_quality:
            for quality in self.reformating_quality:
                # Should raise index error if quality is not in list.
                self.reformating_qualities[quality]
        return db.Model.put(self)


class MaterialInfomation(BaseModel):
    ''' Supporting models of Manifestation.'''
    applied = db.StringProperty(required=False,
                                choices=(["mixed materials",
                                          "acrylic paint",
                                          "chalk",
                                          "charcoal",
                                          "crayon",
                                          "dye",
                                          "gouache",
                                          "graphite",
                                          "ink",
                                          "oil paint",
                                          "pastel",
                                          "plaster",
                                          "tempura",
                                          "water colour"]))
    base = db.StringProperty(required=False,
                             choices=(["bristol board",
                                       "canvas",
                                       "cardboard",
                                       "ceramic",
                                       "glass",
                                       "hard board",
                                       "illustration board",
                                       "ivory",
                                       "leather",
                                       "metal",
                                       "paper",
                                       "parchment",
                                       "plaster",
                                       "plastic",
                                       "porcelain",
                                       "shellac",
                                       "skin",
                                       "stone",
                                       "synthetic",
                                       "textile",
                                       "vellum",
                                       "vinyl",
                                       "wax",
                                       "wood"]))


class ManifestationSingleton(Manifestation):
    ''' FRBRoo class that comprises physical objects that each carry an instance of F2
        Expression, and that were produced as unique objects, with no siblings
        intended in the course of their production. (pg. 29 6/2009 spec.)'''

    def put(self):
        if len(self.expressions) > 1:
            raise db.BadValueError("ManifestationSingleton can only have one "\
                                   " expression. Total expression for this "\
                                   "instance are %s" % len(self.expressions))
        return super(Manifestation,self).put()
                                   
class HandPrintedBook(Manifestation):
    ''' Models a hand-printed book.'''
    book_format = db.StringProperty(required=False,
                                    choices=(["folio",
                                              "4to",
                                              "8v0",
                                              "12 mo",
                                              "16 mo",
                                              "24 mo",
                                              "32 mo",
                                              "48 mo",
                                              "64 mo"]))
    collation = db.StringProperty()
    foliation = db.StringProperty()

class Manuscript(ManifestationSingleton):
    ''' Models a manuscript using RDA Manifestation
        <http://www.rdaonline.org/Manifestation_6_1_09-1.pdf> pg. 22'''
    production_method = db.StringProperty(required=False,
                                          choices=(["holograph",
                                                    "manuscript",
                                                    "printout",
                                                    "carbon copy",
                                                    "photocopy",
                                                    "transcript",
                                                    "handwritten",
                                                    "typewritten"]))

class CartographicResource(Manifestation):
    ''' Models a RDA cartographic resource, from page 19,
        <http://www.rdaonline.org/Manifestation_6_1_09-1.pdf>.'''
    extent_type = db.StringProperty(required=False,
                                    choices=(["atlas",
                                              "diagram",
                                              "globe",
                                              "map",
                                              "model",
                                              "profile",
                                              "remote sensing image",
                                              "section",
                                              "view"]))
    is_plural = db.BooleanProperty()
    layout = db.StringProperty(required=False,
                               choices=(["both sides",
                                         "back to back"]))
    
class Microform(Manifestation):
    '''Models a microform. Carrier values come from
      <http://www.rdaonline.org/Manifestation_6_1_09-1.pdf> page 3

    '''
    carrier = db.StringProperty(required=False,
                                choices=(["aperture card",
                                          "microfiche",
                                          "microfiche cassette",
                                          "microfilm cartridge",
                                          "microfilm cassette",
                                          "microfilm reel",
                                          "microfilm roll",
                                          "microfilm slip",
                                          "microopaque"]))
    emulsion = db.StringProperty(required=False,
                                 choices=(["diazo",
                                           "mixed",
                                           "silver halide",
                                           "vesicular"]))
    generation = db.IntegerProperty()
    polarity = db.IntegerProperty()
    reduction_ratio = ReductionRatioProperty()

class Periodical(Manifestation):
    ''' Differs from FRBR specification as Serial can both be an Expression
        or a Manifestation. To differ between a Serial Expression vs a
        Serial Manifestation, will model a Periodical as a Serial Manifestation.
        Will make implementation easier.

        Includes RDA numbering of serials 
        <http://www.rdaonline.org/Manifestation_6_1_09-1.pdf> pgs.4
    '''
    alphanumeric_first_designation = db.StringProperty()
    alphanumeric_last_designation = db.StringProperty()
    chronological_first_designation = db.StringProperty()
    chronological_last_designation = db.StringProperty()    
    issue = db.StringProperty()
    is_supplement = db.BooleanProperty(default=False)
    is_most_current = db.BooleanProperty(default=False)
    publication_status = db.StringProperty(required=True,
                                           choices=(["current",
                                                     "ceased"]))
    volume = db.StringProperty()

class PrintedBook(Manifestation):
    """ Models a printed book

    """
    isbn = db.ReferenceProperty(MODSData.identifier)
    type_face = db.StringProperty()
    type_size = db.StringProperty()
    rda_font_size = db.StringProperty(required=False,
                                      choices=(["large print",
                                                "giant print"]))

    def put(self):
        if self.isbn:
            if self.isbn.type != 'isbn':
                raise db.BadValueError("PrintedBook.isbn is not an ISBN"\
                                       " identifier, type is %s instead" %\
                                       self.isbn.type)
        return super(PrintedBook,self).put()

class SoundRecording(Manifestation):
    ''' Models a sound recording. Carrier values come from
      <http://www.rdaonline.org/Manifestation_6_1_09-1.pdf> page 3

    '''
    carrier = db.StringProperty(required=False,
                                choices=(["audio cartridge",
                                          "audio cylinder",
                                          "audio disc",
                                          "audio roll",
                                          "audiocassette",
                                          "audiotape reel",
                                          "sound-track reel"]))
    kind_of_cutting = db.StringProperty(required=False,
                                        choices=(["lateral",
                                                  "vertical"]))
    kind_of_sound = db.StringProperty(required=False,
                                      choices=(["monaural",
                                                "stereophonic",
                                                "quadraphonic"]))
    groove_width  = db.StringProperty()
    playing_speed = db.StringProperty()
    playback_channels = db.IntegerProperty()
    recording_type = db.StringProperty(required=False,
                                       choices=(["analog",
                                                 "digital"]))
    recording_medium = db.StringProperty(required=False,
                                         choices=(["magnetic tape",
                                                   "optical disc",
                                                   "born digital"]))
    special_playback_characteristics = db.StringListProperty()
    special_reproduction_characteristic = db.StringProperty(required=False,
                                                            choices=(["DBX",
                                                                      "Dolby",
                                                                      "NAB"]))
    tape_configuration=db.StringProperty()
    
    

class VisualProjection(Manifestation):
    ''' Models visual projection

    '''
    broadcast_standard = db.StringProperty()
    carrier = db.StringProperty(required=False,
                                choices=(["film cartridge",
                                          "film cassette",
                                          "film reel",
                                          "filmslip",
                                          "filmstrip",
                                          "filmstrip cartridge",
                                          "online resource",
                                          "overhead transparency slide",
                                          "stereograph card",
                                          "stereographic disc",
                                          "video cartridge",
                                          "videocassette",
                                          "videodisc",
                                          "videotape reel"]))
    generation = db.IntegerProperty()
    polarity = db.StringProperty(required=False,
                                 choices=(["positive",
                                           "negative",
                                           "mixed"]))
    presentation_format = db.StringProperty(required=False,
                                            choices=(["beta",
                                                      "blu-ray",
                                                      "dvd",
                                                      "hd-dvd",
                                                      "vhs",
                                                      "x-shockwave-flash"]))
    regional_encoding = db.ReferenceProperty(MODSData.hierarchicalGeographic,
                                             collection_name="regional_encoding")
    video_format = db.StringProperty()

class NotatedMusic(Manifestation):
    ''' Models Notated Music following RDA Manifestation page.20
        <http://www.rdaonline.org/Manifestation_6_1_09-1.pdf>'''
    extent_type = db.StringProperty(required=False,
                                    choices=(["score",
                                              "condensed score",
                                              "study score",
                                              "piano conductor part",
                                              "violin conductor part",
                                              "vocal score",
                                              "piano score",
                                              "chorus score",
                                              "choir book",
                                              "table book"]))
    is_plural = db.BooleanProperty()
    layout = db.StringProperty(required=False,
                               choices=(["bar by bar",
                                         "bar over bar",
                                         "line by line",
                                         "line over line",
                                         "melody chord system",
                                         "open score",
                                         "outline",
                                         "paragraph",
                                         "section by section",
                                         "short form scoring",
                                         "single line",
                                         "vertical score"]))

class ElectronicResource(Manifestation):
    ''' Models an electronic resource

    '''
    carrier = db.StringProperty(required=False,
                                choices=(["computer card",
                                          "computer chip cartridge",
                                          "computer disc",
                                          "computer disc cartridge",
                                          "computer tape cartridge",
                                          "computer tape cassette",
                                          "computer tape reel"]))
    encoding_format = db.StringProperty()
    file_requirements = db.ListProperty(db.Key,default=None)
    system_requirements = db.ListProperty(db.Key,default=None)

class Image(ElectronicResource):
    ''' Models an image. Colours, resolution, and kilobytes all
        come from RDA Manifestation page 19
        <http://www.rdaonline.org/Manifestation_6_1_09-1.pdf>'''
    colours = db.StringListProperty(default=None)
    resolution = db.StringProperty()
    kilobytes = db.FloatProperty()

class RemoteAccessElectronicResource(Manifestation):
    """ Models an remote access electronic resource

    """
    access_address = db.ReferenceProperty(MODSData.url,
                                          collection_name="resource_url")
    mode_of_access = db.StringProperty(required=True,
                                       choices=(["internet",
                                                 "www",
                                                 "restricted"]))
    internet_media_type = db.StringProperty()
    is_trial = db.BooleanProperty()
    is_subscription = db.BooleanProperty()
    proxy_address = db.ReferenceProperty(MODSData.url,
                                         collection_name="proxy_url")
    transmission_speed = db.StringProperty()

#==============================================================================#
# Custom FRBR Manifestation Classes for Government Publications                #
#==============================================================================#
class GovernmentPublication(Manifestation):
    """ Models a generic Government publication using controlled vocabulary
    from MARC 008 Books
    <http://www.loc.gov/marc/bibliographic/bd008b.html>
    """
    pub_type = db.StringProperty(required=False,
                                choices=(['autonomous or semi-autonomous',
                                          'federal/national',
                                          'government publication-level undetermined',
                                          'international intergovernmental',
                                          'local',
                                          'multilocal',
                                          'multistate',
                                          'other',
                                          'state, etc.',
                                          'unknown']))

    

class FederalDocument(GovernmentPublication):
    """Models a United States Federal Document"""
    number = db.ReferenceProperty(MODSData.classification,
                                  collection_name="sudoc_callnumber")
    sudoc_item_number = db.StringProperty()
    shipping_lists = db.ListProperty(db.Key)

    def get_lists(self):
        """ returns shipping lists associated with this document. Most
            documents are only associated with a single shippiing list,
            however, documents can appear on multiple lists (i.e.
            once on Paper and then on a Separate."""
        return self.get_collection(property_name="shipping_lists")

    def put(self):
        """ put call sets parent pub_of property to federal/national if
        not set"""
        if not self.pub_type:
            self.pub_type = 'federal/national'
        return super(GovernmentPublication,self).put()
        

class FederalMediaDocument(FederalDocument):
    """ Models a CD-ROM, DVD, or other electronic meda format document."""
    format = db.StringProperty(required=False,
                               choices=(['cdrom',
                                         'dvd',
                                         'floppy',
                                         'other']))

class FederalMicroficheDocument(FederalDocument,Microform):
    """ Models a federal microfiche document. """
    total_items = db.IntegerProperty()

class ColoradoDocument(GovernmentPublication):
    """ Models a Colorado State Document"""
    number = db.ReferenceProperty(MODSData.classification,
                                  collection_name="codoc_callnumber")
    shipping_lists = db.ListProperty(db.Key)
    format = db.StringProperty(required=False,
                               choices=(["paper",
                                         "CD-ROM",
                                         "DVD"]))

    def get_lists(self):
        """ returns list of Colorado Shipping Lists associated with this
            document."""
        return self.get_collection(property_name="shipping_lists")

    def put(self):
        """ put call sets parent pub_of property to state if not set"""
        if not self.pub_type:
            self.pub_type = "state, etc."
        return super(GovernmentPublication,self).put()
    
class Item(BaseModel):
    ''' Item is the lowest level of description of a physical object in
         the FRBR context.

    '''
    access_restrictions = db.ListProperty(db.Key,default=None)
    barcode = db.ReferenceProperty(MODSData.identifier)
    condition = db.StringProperty()
    exhibition_history = db.ListProperty(db.Key,default=None)
    fingerprint = db.StringProperty()
    identifiers = db.ListProperty(db.Key,default=None)
    inscriptions = db.ListProperty(db.Key,default=None)
    is_missing = db.BooleanProperty(default=False)
    location = db.ReferenceProperty(MODSData.location,
                                    collection_name="item_location")
    manifestation = db.ReferenceProperty(Manifestation,
                                         collection_name="exemplified")
    marks = db.ListProperty(db.Key,default=None)
    usage = db.ListProperty(datetime.datetime,default=None)
    owned_by = db.ListProperty(db.Key,default=None)
    provenance = db.ListProperty(db.Key,default=None)
    scheduled_treatment = db.ListProperty(db.Key,default=None) 
    treatment_history = db.ListProperty(db.Key,default=None)

    def put(self):
        if self.barcode:
            if self.barcode.type != 'barcode':
                raise db.BadValueError("Item.barcode's type is not barcode "\
                                       "type is %s" % self.barcode.type)
        if not self.display_form:
            if self.manifestation.title.display_form:
                self.display_form = "%s ITEM" % self.manifestation.title.display_form
        return db.Model.put(self)

class Record(BaseModel):
    ''' Record is a supporting class for storing specific information about
        the creation of FRBR entities .'''
    changed_on = BaseDateProperty()
    creation_date = BaseDateProperty()
    description_conventions = db.ListProperty(db.Key,
                                              default=None)
    entities = db.ListProperty(db.Key,default=None)    
    identifiers = db.StringListProperty()
    language_cataloged_in = db.ReferenceProperty(\
        collection_name='langauge_of_cataloging')
    modifying_agencies = db.ListProperty(db.Key,
                                         default=None)
    org_cataloging_agency = db.ReferenceProperty(\
        collection_name='original_cataloging_agency')
    transcribing_agency = db.ReferenceProperty(\
        collection_name='transcribing_agency')

    def get_description_conventions(self):
        return self.get_collection(property_name='description_conventions')

    def get_modifying_agencies(self):
        return self.get_collection(property_name='modifying_agencies')
                                         

                                         
    
    

class Endeavor(BaseModel):
    ''' Endeavor is a base class representing a single
        Work-Expression-Manifestation-Item object all with 1:1
        relationships for derived objects. Also includes a title
        reference for queries.
    '''
    item = db.ReferenceProperty(Item,
                                collection_name='endeavor_item')
    title = db.ReferenceProperty(MODSData.titleInfo,
                                 collection_name='endeavor_title')

    def put(self):
        endeavor_key = db.Model.put(self)
        endeavor = db.get(endeavor_key)
        endeavor_search = EndeavorSearch(endeavor=endeavor)
        if hasattr(endeavor,'manifestation'):
            note_str = ''
            if self.manifestation.notes:
                for note in self.manifestation.get_notes():
                    note_str += str(note.value)
                endeavor_search.description = db.Text(note_str)
        if self.title:
            endeavor_search.title = self.title.title
        endeavor_search.put()
        return endeavor_key

class AudioEndeavor(Endeavor):
    ''' AlbumEndeavor creates an ER mapping for a song or other
        audio recording.'''
    work = db.ReferenceProperty(MusicalWork,
                                collection_name='audio_work')
    expression = db.ReferenceProperty(Expression,
                                      collection_name="audio_expression")
    manifestation = db.ReferenceProperty(SoundRecording,
                                         collection_name="audio_manifestation")
    

class DissertationEndeavor(Endeavor):
    ''' DissertationEndeavor creates an ER mapping for a Dissertation.'''
    work = db.ReferenceProperty(DissertationThesisWork,
                                collection_name='dissertation_work')
    expression = db.ReferenceProperty(Expression,
                                      collection_name='dissertation_expresion')
    manifestation = db.ReferenceProperty(Manifestation,
                                         collection_name='dissertation_manifestation')

    def put(self):
        if self.work:
            self.work.type = 'dissertation'
        return Endeavor.put(self)


class FilmEndeavor(Endeavor):
    ''' FilmEndeavor creates an ER mapping for projected film.'''
    work = db.ReferenceProperty(Work,
                                collection_name="film_work")
    expression = db.ReferenceProperty(ProjectedImage,
                                      collection_name="film_expression")
    manifestation = db.ReferenceProperty(VisualProjection,
                                         collection_name="film_manifestation")
    

class MapEndeavor(Endeavor):
    ''' MapEndeavor is a specific entity relationship that createas a FRBR
        Entity 1 association for representing a Map.'''
    work = db.ReferenceProperty(CartographicWork,
                                collection_name='map_cartographic_work')
    expression = db.ReferenceProperty(CartographicImageObject,
                                      collection_name=\
                                      'map_cartographic_image_object')
    manifestation = db.ReferenceProperty(CartographicResource,
                                         collection_name=\
                                         'map_cartographic_resource')

    def put(self):
        ''' Presets various entity properties to correspond to a map.'''
        if self.work:
            self.work.form = 'map'
        if self.manifestation:
            self.manifestation.extent_type = 'map'
        return Endeavor.put(self)

class PrintedBookEndeavor(Endeavor):
    ''' PrintedBookEndeavor creates an ER mapping for a printed book.'''
    work = db.ReferenceProperty(PublicationWork,
                                collection_name='printed_book_work')
    expression = db.ReferenceProperty(Expression,
                                      collection_name='printed_book_expression')
    manifestation = db.ReferenceProperty(PrintedBook,
                                         collection_name='printed_book_manifestation')

    def put(self):
        ''' Presets entity properties to correspond to a printed book'''
        if self.work:
            good_work_forms = ["novel",
                               "play",
                               "poem",
                               "essay",
                               "biography"]
            if not good_work_forms.count(self.work.form):
                raise db.BadValueError('PrintedBookEndeavor Work form must '\
                                       'be one of theses values: [%s], value '
                                       'is %s' % (good_work_forms,
                                                  self.work.form))
        if self.expression:
            if self.expression.form != 'text':
                self.expression.form = 'text'
        return Endeavor.put(self)

class PurchasedElectronicResourceEndeavor(Endeavor):
    ''' PurchasedElectronicResource creates an ER mapping for electronic
        databases or other resources that are purchased by an organization.'''
    work = db.ReferenceProperty(IndividualWork,
                                collection_name='purchased_electronic_resource_work')
    expression = db.ReferenceProperty(SelfContainedExpression,
                                      collection_name='purchased_electronic_resource_expression')
    manifestation = db.ReferenceProperty(RemoteAccessElectronicResource,
                                       collection_name='purchased_electronic_resource_manifestation')
    
    
       
class WebSiteEndeavor(Endeavor):
    ''' WebSiteEndeavor creates an ER mapping for a web-site.'''
    work = db.ReferenceProperty(IndividualWork,
                                collection_name="website_work")
    expression = db.ReferenceProperty(SelfContainedExpression,
                                      collection_name="website_expression")
    manifestation = db.ReferenceProperty(RemoteAccessElectronicResource,
                                         collection_name="website_manifestation")

    
    
    
        
  


#=============================================================================#
# FRBR Custom Validation                                                      #
#=============================================================================#
def workExists(work):
    ''' Function takes potential work, uses creator-title pairs to query for
        existing works. If any pair matches, returns existing key.'''
    creator_keys = work.creators
    title_keys = work.titles
    for creator_key in creator_keys:
        for title_key in title_keys:
            query_str = '''SELECT __key__ FROM Work WHERE creators=:1 AND
                           titles=:2'''
            query = db.GqlQuery(query_str,creator_key,title_key)
            existing_work_key = query.get()
            if existing_work_key:
                return existing_work_key
    
def personExists(person,tolerance=None):
    ''' Function takes potential person, queries based on given and family
        names. If matches and if tolerance exists, compares all
        properties between existing and potential person, if passes tolerance,
        assumes existing person and returns key, otherwise returns 
        existing person key. '''
    query = db.GqlQuery('''SELECT * FROM Person WHERE family=:1 AND
                           given=:2''',
                        person.family,person.given)
    existing_person = query.get()
    if existing_person:
        if tolerance:
            if compareModels(existing_person,person) >= tolerance:
                return existing_person.key()
        else:
            return existing_person.key()

def corporateBodyExists(corp_body,tolerance=None):
    ''' Function takes potential corporate body, queries datastore based on
        name of corporate body. If matches and passes existing
        tolerance, returns key of corporate body, otherwise returns nothing.'''
    query = db.GqlQuery('''SELECT * FROM CorporateBody WHERE name=:1''',
                        corp_body.name)
    existing_corp_body = query.get()
    if existing_corp_body:
        if tolerance:
            if compareModels(existing_corp_body,corp_body) >= tolerance:
                return existing_corp_body.key()
        else:
            return existing_corp_body.key()

def eventExists(event,tolerance=None):
    ''' Function takes potential event, queries datastore based on name of
        potential event. If matches and passes existing tolerance, returns
        key of matched event, otherwise returns nothing.'''
    query = db.GqlQuery('''SELECT * FROM Event WHERE name=:1''',
                        event.name)
    existing_event = query.get()
    if existing_event:
        if tolerance:
            if compareModels(existing_event,event) >= tolerance:
                return existing_event.key()
        else:
            return existing_event.key()
